﻿using System;
using System.ComponentModel;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using Library.Winform.Draw.Tools;
using Library.Util;
namespace Library.Winform.Draw {
	public abstract class ObjectDraw : IObjectDraw {

		private static readonly object EventMouseClicked = new object();
		private static readonly object EventMouseDoubleClicked = new object();
		private static readonly object EventMoved = new object();
		private static readonly object EventResized = new object();
		private static readonly object EventDoubleClicked = new object();
		private static readonly object EventMoveCompleted = new object();
		private static readonly object EventResizeCompleted = new object();
		public const UInt16 NoGroupId = 0;
		private UInt16 _groupId;
		private UInt16 _penWidth;
		private bool _visible;
		private bool _selected;
		/// <summary>
		/// Flag stating if the current instance is allready disposed.
		/// </summary>
		private bool _disposed;




		/// <summary>
		/// GroupId is 0 meaning no grouping, otherwise it belongs to group
		/// </summary>
		public UInt16 GroupId { get { return _groupId; } internal set { _groupId = value; } }
		/// <summary>
		/// Get or Set Draw visible;
		/// </summary>
		public bool Visible {
			get { return _visible; }
			set { _visible = value; }
		}

		/// <summary>
		/// Get or set Draw enable
		/// </summary>
		public virtual bool Enable {
			get;
			set;
		}
		protected readonly EventHandlerList Events;

		protected ObjectDraw() {
			InitValue();
			Events = new EventHandlerList();
		}

		private void InitValue() {
			PenColor = Color.Black;
			_penWidth = 1;
			GraphicsContainer = null;
			_rectangleBoundSize = 6;
			_groupId = NoGroupId;
			_selected = false;
			_shadowLen = 0;
			ShadowColor = Color.Empty;
			_visible = true;
			Enable = true;

		}

		public Color ShadowColor { get; set; }
		private UInt16 _shadowLen;
		public UInt16 ShadowLen {
			get { return _shadowLen; }
			set {
				if (value > 0) {
					if (ShadowColor == Color.Empty) {
						ShadowColor = Color.FromArgb(70, Color.Black);
					}
				}
				_shadowLen = value;
			}
		}
		public virtual void ReadFrom(Stream stream) {
			int args;
			stream.Read(out args);
			PenColor = Color.FromArgb(args);
			stream.Read(out _penWidth);
			stream.Read(out _rectangleBoundSize);
			stream.Read(out _groupId);
			stream.Read(out _selected);
			stream.Read(out _visible);

			stream.Read(out _shadowLen);
			if (_shadowLen > 0) {
				stream.Read(out args);
				ShadowColor = Color.FromArgb(args);
			}

			bool enable;
			stream.Read(out enable);
			Enable = enable;
		}

		public virtual void WriteTo(Stream stream) {
			stream.Write(PenColor.ToArgb());
			stream.Write(_penWidth);
			stream.Write(_rectangleBoundSize);
			stream.Write(_groupId);
			stream.Write(_selected);
			stream.Write(_visible);
			stream.Write(_shadowLen);
			if (_shadowLen > 0) {
				stream.Write(ShadowColor.ToArgb());
			}
			stream.Write(Enable);

		}

		public bool HasGroup { get { return GroupId != NoGroupId; } }

		/// <summary>
		/// Move handle point to new position, and regenerate max position
		/// </summary>
		/// <param name="pointHandler"></param>
		/// <param name="newPoint"></param>

		public void MovePointHandleTo(ushort pointHandler, Point newPoint) {
			var oldLocation = Location;
			MoveHandleTo(pointHandler, newPoint);
			var newLocation = Location;
			OnResized(new DrawObjectResizedEventArgs { OffsetX = newLocation.X - oldLocation.X, OffsetY = newLocation.Y - oldLocation.Y, NewPoint = newPoint, PointHandler = pointHandler });
		}
		/// <summary>
		/// Move handle point to new position, and regenerate max position
		/// </summary>
		/// <param name="pointHandler"></param>
		/// <param name="newPoint"></param>
		/// <returns></returns>
		protected abstract void MoveHandleTo(ushort pointHandler, Point newPoint);


		public virtual bool Selected {
			get { return _selected; }
			set {
				//do not allow select object when
				if (!Enable || !Visible)
					return;

				if (value != _selected) {
					if (HasGroup) {
						int groupMember = 0;
						for (int i = 0; i < GraphicsContainer.GraphicsList.Count; i++) {
							var grap = GraphicsContainer.GraphicsList[i];
							if (grap.GroupId == GroupId) {
								grap._selected = value;
								groupMember++;
							}
						}

						if (groupMember == 1) {
							GroupId = NoGroupId;
						}
					}
					_selected = value;
				}
			}
		}
		/// <summary>
		/// Pen width, accept value in ranges [Int16.Min, Int16.Max]
		/// </summary>
		public UInt16 PenWidth { get { return _penWidth; } set { _penWidth = value; } }

		/// <summary>
		/// Pen color
		/// </summary>
		public Color PenColor { get; set; }
		/// <summary>
		/// Array of point object handle. A handle point is drawn with small retangle and user can do some
		/// resize action on that point.
		/// each element of handle point must be bigger 0 (> 0)
		/// </summary>
		public abstract ushort[] HandlePoints { get; }
		/// <summary>
		/// Normalize object.
		/// Call this function in the end of object resizing.
		/// </summary>
		public virtual void Normalize() {

		}

		public IDrawContainer GraphicsContainer {
			get;
			internal set;
		}

		public Point ScrollOffset {
			get {
				if (GraphicsContainer == null)
					return Point.Empty;
				return GraphicsContainer.ScrollOffset;
			}
		}

		public abstract Point MaxPosition { get; }
		public abstract Point MinPosition { get; }

		public virtual ContextMenuStrip ContextMenuStrip {
			get;
			set;
		}

		public Point Location {
			get { return GetLocation(); }
			set {
				var location = GetLocation();
				if (value != location) {
					Move(value.X - location.X, value.Y - location.Y);
				}
			}
		}

		public void Refresh() {
			Draw(GraphicsContainer.CreateGraphics());
			var min = MinPosition;
			var max = MaxPosition;
			int boundSize = Selected ? RectangleBoundSize / 2 : 0;
			var x = min.X = ScrollOffset.X - boundSize;
			var y = min.Y - ScrollOffset.Y - boundSize;
			var width = max.X - min.X + boundSize;
			var height = max.Y - min.Y + boundSize;
			GraphicsContainer.Update(x, y, width, height);
		}

		public object Tag {
			get;
			set;
		}


		protected abstract Point GetLocation();


		/// <summary>
		/// Draw object
		/// </summary>
		/// <param name="g"></param>

		public void Draw(Graphics g) {
			if (!Visible) return;
			DrawMe(g);
			DrawSlectedBound(g);
		}

		private Int16 _rectangleBoundSize;
		/// <summary>
		/// for calculate GetRecBoundToPoint
		/// </summary>
		protected Int16 RectangleBoundSize { get { return _rectangleBoundSize; } set { _rectangleBoundSize = value; } }


		protected abstract void DrawMe(Graphics g);

		/// <summary>
		/// Get rectange bound to point (point is center)
		/// </summary>
		/// <param name="point"></param>
		/// <returns></returns>
		protected virtual Rectangle GetRecBoundToPoint(Point point) {
			return GetRecBoundToPoint(point.X, point.Y);
		}
		protected virtual Rectangle GetRecBoundToPoint(int x, int y) {
			int size = RectangleBoundSize / 2;
			return new Rectangle(x - size, y - size, RectangleBoundSize, RectangleBoundSize);
		}
		protected virtual void DrawSlectedBound(Graphics g) {
			if (!Selected)
				return;
			using (var brush = new SolidBrush(Color.Black)) {
				foreach (var handlePoint in HandlePoints) {
					var point = GetPointFromHandle(handlePoint);
					g.FillRectangle(brush, GetRecBoundToPoint(point.X - ScrollOffset.X, point.Y - ScrollOffset.Y));
				}
			}
		}

		/// <summary>
		/// Test for point belong to object, if object is not visible, always retuen -1
		/// </summary>
		/// <param name="point">Point</param>
		/// <returns>return -1: No hit (hit out side of object). return 0: hit on object. return >0: hit at corner object (for resize)</returns>

		public short HitTest(Point point) {
			if (!Visible) return -1;
			if (Selected) {
				foreach (var handlePoint in HandlePoints) {
					var pointHandle = GetPointFromHandle(handlePoint);
					var rec = GetRecBoundToPoint(pointHandle);
					if (rec.Contains(point)) return (short)handlePoint;
				}
			}

			if (ContainPoint(point))
				return 0;

			return -1;
		}

		public virtual Cursor GetHandleCursor(ushort pointHandler) {
			return Cursors.Default;
		}

		public virtual Point GetPointFromHandle(ushort pointHandler) {
			throw new ArgumentException(String.Format("Object: {0} not support PointHalder: {1}", GetType(), pointHandler));
		}


		public virtual bool ContainPoint(Point point) {
			return false;
		}

		/// <summary>
		/// Move object and regenerate max position
		/// </summary>
		/// <param name="offsetX"></param>
		/// <param name="offsetY"></param>

		public virtual void Move(int offsetX, int offsetY) {
			MoveTo(offsetX, offsetY);
			OnMoved(new DrawObjectMovedEventArgs { OffsetX = offsetX, OffsetY = offsetY });
		}

		protected abstract void MoveTo(int offsetX, int offsetY);


		/// <summary>
		/// object change PointHandles, so Hit Graphic needed to renew
		/// Hit graphic
		/// is released and should be created again.
		/// </summary>
		protected virtual void DisposeHitGraphic() {
		}

		///// <summary>
		///// Get cursor for the handle
		///// </summary>
		///// <param name="pointHandleIndex"></param>
		///// <returns></returns>
		//public virtual Cursor GetHandleCursor(Int16 pointHandleIndex) {
		//  return Cursors.Default;
		//}

		/// <summary>
		/// Test whether object intersects with rectangle
		/// </summary>
		/// <param name="rectangle"></param>
		/// <returns></returns>
		public virtual bool IntersectsWith(Rectangle rectangle) {
			return false;
		}


		internal void FireClickEvent(ScrollMouseEventArgs e) {
			OnMouseClicked(e);
		}

		internal void FireDoubleClickEvent(EventArgs e) {
			OnDoubleClicked(e);
		}
		internal void FireMouseDoubleClickEvent(ScrollMouseEventArgs e) {
			OnMouseDoubleClicked(e);

		}

		public event EventHandler<ScrollMouseEventArgs> MouseClicked {
			add {
				Events.AddHandler(EventMouseClicked, value);
			}
			remove {
				Events.RemoveHandler(EventMouseClicked, value);
			}
		}
		public event EventHandler<ScrollMouseEventArgs> MouseDoubleClicked {
			add { Events.AddHandler(EventMouseDoubleClicked, value); }
			remove { Events.RemoveHandler(EventMouseDoubleClicked, value); }
		}
		public event EventHandler<DrawObjectMovedEventArgs> Moved {
			add { Events.AddHandler(EventMoved, value); }
			remove { Events.RemoveHandler(EventMoved, value); }
		}
		public event EventHandler<DrawObjectResizedEventArgs> Resized {
			add { Events.AddHandler(EventResized, value); }
			remove { Events.RemoveHandler(EventResized, value); }
		}
		public event EventHandler DoubleClicked {
			add { Events.AddHandler(EventDoubleClicked, value); }
			remove { Events.RemoveHandler(EventDoubleClicked, value); }
		}

		public event EventHandler MoveCompleted {
			add { Events.AddHandler(EventMoveCompleted, value); }
			remove { Events.RemoveHandler(EventMoveCompleted, value); }
		}

		public event EventHandler ResizeCompleted {
			add { Events.AddHandler(EventResizeCompleted, value); }
			remove { Events.RemoveHandler(EventResizeCompleted, value); }
		}

		protected virtual void OnResized(DrawObjectResizedEventArgs e) {
			var handler = Events[EventResized] as EventHandler<DrawObjectResizedEventArgs>;
			if (handler != null) handler(this, e);
		}

		protected virtual void OnMoved(DrawObjectMovedEventArgs e) {
			var handler = Events[EventMoved] as EventHandler<DrawObjectMovedEventArgs>;
			if (handler != null) handler(this, e);
		}

		protected virtual void OnMouseDoubleClicked(ScrollMouseEventArgs e) {
			var handler = Events[EventMouseDoubleClicked] as EventHandler<ScrollMouseEventArgs>;
			if (handler != null) handler(this, e);
		}


		protected internal virtual void OnMoveCompleted(EventArgs e) {
			var hander = Events[EventMoveCompleted] as EventHandler;
			if (hander != null) {
				hander(this, e);
			}
		}

		protected internal virtual void OnResizeCompleted(EventArgs e) {
			var handler = Events[EventResizeCompleted] as EventHandler;
			if (handler != null) {
				handler(this, e);
			}
		}


		protected virtual void OnDoubleClicked(EventArgs e) {
			var handler = Events[EventDoubleClicked] as EventHandler;
			if (handler != null) handler(this, e);
		}

		protected virtual void OnMouseClicked(ScrollMouseEventArgs e) {
			var handler = Events[EventMouseClicked] as EventHandler<ScrollMouseEventArgs>;
			if (handler != null) handler(this, e);
		}

		protected void WriteColorToStream(Stream stream, Color color) {
			stream.Write(color.ToArgb());
		}

		protected Color ReadColorFromStream(Stream stream) {
			int color;
			stream.Read(out color);
			return Color.FromArgb(color);
		}

		#region Implementation of IDisposable
		// Use C# destructor syntax for finalization code.
		// This destructor will run only if the Dispose method
		// does not get called.
		// It gives your base class the opportunity to finalize.
		// Do not provide destructors in types derived from this class.
		~ObjectDraw() {
			// Do not re-create Dispose clean-up code here.
			// Calling Dispose(false) is optimal in terms of
			// readability and maintainability.
			Dispose(false);
		}

		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);

		}
		/// <summary>
		/// Dispose method, releasing all managed resources.
		/// </summary>
		/// <param name="disposing">States if the resources should be disposed.</param>
		protected virtual void Dispose(bool disposing) {
			// Check to see if Dispose has already been called.
			if (!_disposed) {
				// If disposing equals true, dispose all managed
				// and unmanaged resources.
				if (disposing) {
					// Dispose managed resources.
					//component.Dispose();
					Events.Dispose();
				}

				// Call the appropriate methods to clean up
				// unmanaged resources here.
				// If disposing is false,
				// only the following code is executed.
				// Note disposing has been done.
				_disposed = true;

			}

		}

		#endregion

		#region Implementation of ICloneable
		/// <summary>
		/// Clone me. Do not clone events
		/// </summary>
		/// <returns></returns>
		public abstract object Clone();
		/// <summary>
		/// Fill my property to other. Not Fill Events
		/// </summary>
		/// <param name="objectDraw"></param>
		protected void FillOriginalTo(ObjectDraw objectDraw) {
			objectDraw._visible = _visible;
			objectDraw._shadowLen = _shadowLen;
			objectDraw._selected = _selected;
			objectDraw._rectangleBoundSize = _rectangleBoundSize;
			objectDraw._penWidth = _penWidth;
			objectDraw._groupId = _groupId;
		}

		#endregion
	}
}
